package com.example

import java.io.{BufferedInputStream, BufferedOutputStream, DataInputStream, DataOutputStream, FileInputStream, FileOutputStream, IOException}
import scala.collection.mutable

object QTreeSerializer {

  // 序列化标记
  private val LEAF_NODE_MARKER: Byte = 0
  private val INTERNAL_NODE_MARKER: Byte = 1

  /**
   * 自定义序列化方法（BFS顺序）
   * 格式：
   *   [节点类型标记][bounds][可选数据]
   *   叶子节点: 0 [x:Int][y:Int][size:Int][value:Boolean]
   *   内部节点: 1 [x:Int][y:Int][size:Int]
   */
  def serializeCustom(root: QuadTree, filePath: String): Unit = {
    val dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filePath)))
    val queue = mutable.Queue[QuadTree](root)

    try {
      while (queue.nonEmpty) {
        val node = queue.dequeue()
        node match {
          case Leaf(state, bounds) =>
            // 写入叶子节点标记
            dos.writeByte(LEAF_NODE_MARKER)
            // 写入bounds
            writeBounds(dos, bounds)
            // 写入值
            val bVal = if (state == Empty) true else false
            dos.writeBoolean(bVal)
          case n: Node =>
            dos.writeByte(INTERNAL_NODE_MARKER)  // 写入内部节点标记
            writeBounds(dos, n.bound)  // 写入bounds
            // 将子节点加入队列（保证顺序）
            queue.enqueue(n.nw)
            queue.enqueue(n.ne)
            queue.enqueue(n.sw)
            queue.enqueue(n.se)
          case _ => // do nothing.
        }
      }
    } finally {
      dos.close()
    }
  }

  // 辅助方法：写入Bounds
  private def writeBounds(dos: DataOutputStream, bounds: Bounds): Unit = {
    dos.writeInt(bounds.x)
    dos.writeInt(bounds.y)
    dos.writeInt(bounds.size)
  }

  /**
   * 自定义反序列化方法
   */
  def deserializeCustom(filePath: String): QuadTree = {
    val dis = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath)))
    val nodeQueue = mutable.Queue[(Node, Int)]() // (父节点, 子节点索引)
    var root: QuadTree = null

    try {
      while (dis.available() > 0) {
        val marker = dis.readByte()
        val bounds = readBounds(dis)

        val n = marker match {
          case LEAF_NODE_MARKER =>
            val value = dis.readBoolean()
            val status = if (value) Empty else Obstacle
            Leaf(status, bounds)
          case INTERNAL_NODE_MARKER =>
            // 创建临时内部节点（4个子元素先置空）
            val node = new Node(null, null, null, null, bounds)
            // 加入队列等待填充子节点
            nodeQueue.enqueue((node, 0))
            node
          case _ => throw new IOException("Invalid marker byte: " + marker)
        }

        // 设置根节点或填充父节点的子节点
        if (root == null) {
          root = n
        } else {
          fillParentChild(nodeQueue, n)
        }
      }
    } finally {
      dis.close()
    }

    root
  }

  // 辅助方法：读取Bounds
  private def readBounds(dis: DataInputStream): Bounds = {
    val x = dis.readInt()
    val y = dis.readInt()
    val size = dis.readInt()
    Bounds(x, y, size)
  }

  // 辅助方法：填充父节点的子节点
  private def fillParentChild(queue: mutable.Queue[(Node, Int)], node: QuadTree): Unit = {
    if (queue.nonEmpty) {
      val (parent, index) = queue.head

      // 子节点的写入顺序是：n.nw n.ne n.sw n.se
      index match {
        case 0 => parent.nw = node
        case 1 => parent.ne = node
        case 2 => parent.sw = node
        case 3 => parent.se = node
      }

      if (index == 3) {
        queue.dequeue() // 已填充完所有子节点
      } else {
        queue.update(0, (parent, index + 1)) // 更新索引
      }
    }
  }

  def main(args: Array[String]): Unit = {
    println("We are now running in QTreeSerializer.main.")
    println("we are going to:")
    println("- Deserialize QTree from a binary file(read from serializeBinary.data, map size is 512x512).")
    println("- Visualize QTree in grids.png")
    println("- Visualize neighborhoods in grids.png")
    println("- Visualize path finding in grids.png")

    val quadTree = QTreeSerializer.deserializeCustom("D:\\temp\\jl\\serializeBinary.data")
    QTreeFromGrid.computeAllNeighbors(quadTree)

    // 寻路测试
    val route = AStar.findPath(quadTree, 110, 338, 484, 372)
    println(route)

    QTreeRender.renderQuadtree(quadTree, 512, "D:\\temp\\jl\\grids.png", route match {
      case Some(value) => value
      case _ => List[(Int, Int)]()
    })
  }
}